package com.craftinginterpreters.lox;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import static com.craftinginterpreters.lox.TokenType.*;

public class Scanner {
    final String source;
    final List<Token> tokens = new ArrayList<>();
    private int start = 0;          // 词元的开始位置
    private int current = 0;        // 当前字符的位置
    private int line = 1;           // 当前字符的行号

    private static final Map<String, TokenType> keywords;
    static {
        keywords = new HashMap<>();
        keywords.put("and", AND);
        keywords.put("class", CLASS);
        keywords.put("else", ELSE);
        keywords.put("false", FALSE);
        keywords.put("for", FOR);
        keywords.put("fun", FUN);
        keywords.put("if", IF);
        keywords.put("nil", NIL);
        keywords.put("or", OR);
        keywords.put("print", PRINT);
        keywords.put("return", RETURN);
        keywords.put("super", SUPER);
        keywords.put("this", THIS);
        keywords.put("true", TRUE);
        keywords.put("var", VAR);
        keywords.put("while", WHILE);
    }

    Scanner(String source) {
        this.source = source;
    }

    public List<Token> scanTokens() {
        while (!isAtEnd()) {
            // 记录词元的开始位置
            start = current;
            scanToken();
        }
        // 添加EOF哨兵记号作为词法分析结束的标志
        tokens.add(new Token(TokenType.EOF, "", null, line));
        return tokens;
    }

    // 扫描一个记号
    private void scanToken() {
        char c = advance(); 

        switch (c) {
            case '(': addToken(LEFT_PAREN); break;
            case ')': addToken(RIGHT_PAREN); break;
            case '{': addToken(LEFT_BRACE); break;
            case '}': addToken(RIGHT_BRACE); break;
            case ',': addToken(COMMA); break;
            case '.': addToken(DOT); break;
            case '-': addToken(MINUS); break;
            case '+': addToken(PLUS); break;
            case ';': addToken(SEMICOLON); break;
            case '*': addToken(STAR); break; 
            case '!': addToken(match('=') ? BANG_EQUAL : BANG); break;
            case '=': addToken(match('=') ? EQUAL_EQUAL : EQUAL); break;
            case '<': addToken(match('=') ? LESS_EQUAL : LESS); break;
            case '>': addToken(match('=') ? GREATER_EQUAL : GREATER); break;
            case '/':
                if (match('/')) {
                    // 注释直到行末
                    while (peek() != '\n' && !isAtEnd()) {
                        advance();
                    }
                } else {
                    addToken(SLASH);
                }
                break;
            case ' ':
            case '\r':
            case '\t':
                // 跳过空白字符
                break;
            case '\n':
                line++;
                break;
            case '"':
                string();
                break;
            default:
                if (isDigit(c)) {
                    number();
                } else if (isAlpha(c)) {
                    identifier();
                } else {
                    Lox.error(line, "Unexpected character.");
                }
                break;
        }
    }

    // 扫描标识符
    private void identifier() {
        while (isAlphaNumeric(peek())) {
            advance();
        }
        String text = source.substring(start, current);
        TokenType type = keywords.get(text);
        if (type == null) {
            type = IDENTIFIER;
        }
        addToken(type);
    }

    // 扫描数字字面量
    private void number() {
        while (isDigit(peek())) {
            advance();
        }

        // 如果当前字符是小数点，则继续扫描
        if (peek() == '.' && isDigit(peekNext())) {
            advance();
            while (isDigit(peek())) {
                advance();
            }
        }

        addToken(NUMBER, Double.parseDouble(source.substring(start, current)));
    }

    // 扫描字符串字面量
    private void string() {
        // 如果没有遇到右引号, 一直循环
        while (peek() != '"' && !isAtEnd()) {
            if (peek() == '\n') {
                line++;
            }
            advance();
        }

        // 还不能结束，因为需要跳过右引号
        if (isAtEnd()) {
            Lox.error(line, "Unterminated string.");
            return;
        }

        // 跳过右引号
        advance();

        addToken(STRING, source.substring(start + 1, current - 1));
    }

    // 当前字符是否匹配给定的字符， 如果匹配则移动到下一个字符
    private boolean match(char expected) {
        if (isAtEnd() || source.charAt(current) != expected) {
            return false;
        }
        current++;
        return true;
    }

    // 获取当前字符
    private char peek() {
        if (isAtEnd()) {
            return '\0';
        }
        return source.charAt(current);
    }

    // 获取下一个字符
    private char peekNext() {
        if (current + 1 >= source.length()) {
            return '\0';
        }
        return source.charAt(current + 1);
    }

    // 当前字符是否是数字
    private boolean isDigit(char c) {
        return c >= '0' && c <= '9';
    }

    // 当前字符是否是字母
    private boolean isAlpha(char c) {
        return (c >= 'a' && c <= 'z') ||
               (c >= 'A' && c <= 'Z') ||
               c == '_';
    }

    // 当前字符是否是字母或数字
    private boolean isAlphaNumeric(char c) {
        return isAlpha(c) || isDigit(c);
    }

    // 是否消费完所有字符
    private boolean isAtEnd() {
        return current >= source.length();
    }

    // 获取当前字符并移动到下一个字符
    private char advance() {
        return source.charAt(current++);
    }

    // 添加一个记号
    private void addToken(TokenType type) {
        addToken(type, null);
    }

    // 添加一个记号，并指定它的字面量值
    private void addToken(TokenType type, Object literal) {
        String text = source.substring(start, current);     // 词元的文本
        tokens.add(new Token(type, text, literal, line));
    }
}
